from ._rely import * # 导入三方依赖库
from ._ntype import TextType # 文本域支持格式化类型
from ._exception import * # 异常库
from ._collections import * # 自定义类型

from typing import Any, Dict, List, OrderedDict as Type_OrderedDict, Tuple
from functools import reduce
from copy import deepcopy as _deepcopy

ATTR_PREFIX = '@' # 默认的属性前缀（用于 xmltodict 的内部解析）
CDATA_KEY = '#text' # 默认的文本域索引名称（用于 xmltodict 的内部解析）
REAL_CDATA_KEY = 'text_' # xmltocd 的文本域索引默认名称

"""
之所以主要代码中不用中文写注释，是因为防止在某些操作系统中会呈现乱码。
此文档存在的意义就是提供中文的注释代码。
"""

class ChainXML:

    xml = None # 整个 XML 对象

    def __init__(self
                 , doc: Type_OrderedDict # 从 xmltodict 读取过来的数据包
                 , attr_prefix: str = ATTR_PREFIX
                 , cdata_key: str = CDATA_KEY
                 , real_cdata_key = REAL_CDATA_KEY
                 ) -> None:

        if not isinstance(doc, OrderedDict) or 1 != len(doc): # 有且只能有一个根节点
            raise DocTypeError('`doc` type error. Only be one root.')

        self.doc = doc
        self.attr_prefix = attr_prefix
        self.cdata_key = cdata_key
        self.real_cdata_key = real_cdata_key

        # ===========================================================
        ## 属性搜索的依据
        '''self._attr_searcher: add-attr modify-attr del-attr del-node

            {
                'name=ok': {
                    'count': 0,
                    'nodes': [
                        id(node),
                        ...
                    ]
                }
            }
        '''
        self._attr_searcher = {}

        # ===========================================================
        ## 保存时的属性键值还原与反还原，还原成 xmltodict 中的属性解析格式
        '''self._reverse_attr_name: add-attr del-attr del-node
            {
                ("name", "#name"): [
                    id(node),
                    ...
                ],
                ...
            }
        '''
        self._reverse_attr_name = {}

        # ===========================================================
        ## 节点地址 和 节点数据包 的绑定
        '''self._id_nodes: add-node del-node
            {
                id(node): node, # node: ChainDict
                ...
            }
        '''
        self._id_nodes = {}

        # ===========================================================
        ## 节点地址 和 标签名称 的绑定
        '''self._id_tags: add-node del-node
            {
                id(node): Tag, # Tag: str
                ...
            }
        '''
        self._id_tags = {}

        # ===========================================================
        ## 标签名称 与 节点地址 的绑定
        '''self._tag_ids: add-node del-node
            {
                Tag: [
                    id(node),
                    ...
                ], # Tag: str
                ...
            }
        '''
        self._tag_ids = {}

        # ===========================================================
        ## 文本域的文本 与 节点地址 的绑定
        '''self._text_ids: modify-text add-node del-node
            {
                'Text': [
                    id(node),
                    ...
                ], # Text: str
                ...
            }
        '''
        self._text_ids = {}
        
        # ===========================================================
        ## 孩子节点地址 与 其父节点地址 的绑定
        '''self._contrast_ids: add-node del-node
            {
                id(sub_node): id(parent_node),
                ...
            }
        '''
        self._contrast_ids = {}

        # ===========================================================
        self.xml = self._build_chain_by_recursion(self.doc)

    def __op_attr_searcher(self, op=-1):
        ...
    
    def __op_reverse_attr_name(self, op=-1):
        ...

    def __op_id_nodes(self, op=-1):
        ...

    def __op_id_tags(self, op=-1):
        ...

    def __op_tag_ids(self, op=-1):
        ...

    def __op_text_ids(self, op=-1):
        ...

    def __op_contrast_ids(self, op=-1):
        ...

    def registry_id_nodes(self, node: Any) -> None:
        self._id_nodes[id(node)] = node

    def registry_contrast_ids(self, sub_node: Any, node: ChainDict) -> None:
        self._contrast_ids[id(sub_node)] = id(node)

    def registry_text_ids(self, text: str, node: Any) -> None:
        if text in self._text_ids:
            if id(node) not in self._text_ids[text]:
                self._text_ids[text].append(id(node))
            else:
                raise Exception('Fatal error in parser! Do not re register.')
        else:
            self._text_ids[text] = [id(node), ]

    def registry_id_tags(self, node: Any, tag: str) -> None:
        self._id_tags[id(node)] = tag

    def registry_tag_ids(self, tag: str, node: Any) -> None:
        if tag in self._tag_ids:
            if id(node) not in self._tag_ids[tag]:
                self._tag_ids[tag].append(id(node))
            else:
                raise Exception('Fatal error in parser! Do not re register.')
        else:
            self._tag_ids[tag] = [id(node), ]

    def registry_attr_searcher(self, search_key: str, node: Any) -> None:
        if search_key in self._attr_searcher:
            if id(node) not in self._attr_searcher[search_key]['nodes']:
                self._attr_searcher[search_key]['nodes'].append(id(node))
                self._attr_searcher[search_key]['count'] += 1
            else:
                raise Exception('Fatal error in parser! Do not re register.')
        else:
            self._attr_searcher[search_key] = {
                'count': 1,
                'nodes': [id(node), ]
            }

    def registry_reverse_attr_name(self, new_old: Tuple[str, str], node: Any) -> None:
        if new_old not in self._reverse_attr_name:
            self._reverse_attr_name[new_old] = [id(node), ]
        else:
            if id(node) not in self._reverse_attr_name[new_old]: # 非重复性添加
                self._reverse_attr_name[new_old].append(id(node))
            else:
                raise Exception('Fatal error in parser! Do not re register.')

    '''信号接收处理函数
    '''
    def signal_del_node(self):
        self._attr_searcher
        self._reverse_attr_name
        self._id_nodes
        self._id_tags
        self._tag_ids
        self._text_ids
        self._contrast_ids
        
    def signal_add_node(self, sub_node: Any, node: Any, tag: str=None, text: str=None) -> None:
        '''新增一个节点，需要分为五个步骤处理
        '''
        self.registry_id_nodes(sub_node)
        self.registry_id_tags(sub_node, tag)
        self.registry_contrast_ids(sub_node, node)
        if tag is not None: # list 类型的节点，没有 tag 标签
            self.registry_tag_ids(tag, node[tag])
        if text is not None: # 主要用于区分 ChainDict 和 List
            self.registry_text_ids(text, sub_node)

    def signal_add_attr(self, node: Any, new_old: Tuple[str, str], search_key: str):
        '''新增属性时触发
        '''
        self.registry_reverse_attr_name(new_old, node)
        self.registry_attr_searcher(search_key, node)

    def signal_modify_attr(self):
        self._attr_searcher

    def signal_del_attr(self):
        self._reverse_attr_name
        self._attr_searcher

    def signal_modify_text(self):
        self._text_ids

    
    def _build_chain_by_recursion(self, doc_obj: Any) -> ChainDict:
        '''递归式构造数据包

        解析原则：注册总在构建结构之后
        '''
        if doc_obj is None: # 空标签的情况：<s></s> 这种，会将节点解析为 None
            temp_node = ChainDict()
            return temp_node

        if not isinstance(doc_obj, dict): # 非字典类型处理（之所以不是 ChainDict 是因为 doc_obj 是 xmltodict 的产出物，非本程序的产出数据）
            if isinstance(doc_obj, (list, tuple, )): # 如果在 xmltodict 中是列表，则在本程序中也依旧是列表类型，但是内部数据会重新构造
                temp_list = []
                for line in doc_obj:
                    each = self._build_chain_by_recursion(line)

                    """数据包的二次处理，必定是在构建节点之后
                    """
                    if not isinstance(each, str): # ChainDict 或者 List 类型
                        '''此处不需要注册节点地址，是因为已经在 刚创建时 注册过了
                        '''
                        self.registry_contrast_ids(each, temp_list) # 必须步骤 -- 注册节点
                        temp_list.append(each)

                        # 如果是 ChainDict 类型，追查是否含有 'text_' 属性（理论上一定有，没有，为了统一，程序也会构造一个出来）
                        # 如果有，保存 文本域文本 对应的 节点地址。
                        if isinstance(each, ChainDict) and self.real_cdata_key in each: # 在此处只要是 ChainDict 就一定有 'text_' 属性
                            self.registry_text_ids(each[self.real_cdata_key], each) # 一定是列表，文本域文本 和 节点地址 是 一对多 的关系

                    else: # 此分支下一定是 非属性、非文本域 的字符串。（解决 xmltodict 解析时将 <t>12</t> 节点解析为 {'t': 12} 的情况）仅列表情况下适用
                        temp_node = ChainDict()
                        temp_node[self.real_cdata_key] = each # 统一节点文本域的结构，统一可以用 .text_ 来获取值
                        temp_list.append(temp_node)
                        self.signal_add_node(temp_node, temp_list, text=each)
                return temp_list

            return doc_obj # 此处定返回字符串，但不能二次处理，因为此处无法确定返回字符串的性质[属性/文本域/特殊文本域]（移到下方处理）

        temp_xml = ChainDict() # 从根出发
        self.registry_id_nodes(temp_xml) # 此节点可以理解为构造父节点（此节点为外部的空壳，没有数据意义，但有着结构意义，需要单独注册）
        
        for k, v in doc_obj.items(): # 从根开始遍历

            newname_oldname = None # 新旧属性名（就一个前缀的区别） 也用于标记是否是属性
            _text_ = False # 标记是否是 文本域

            if k.startswith(self.attr_prefix): # 是否是属性
                new_name = k[len(self.attr_prefix):]
                newname_oldname = (new_name, k) # 新前旧后
                k = new_name

            if self.cdata_key == k: # 是否是文本域
                k = self.real_cdata_key
                _text_ = True
            
            temp_node_content = self._build_chain_by_recursion(v) # 先将所有的架子搭好，然后再处理细节

            '''下面是每个数据节点的细节处理
            '''
            if not (newname_oldname is not None or _text_) and isinstance(temp_node_content, str): # 虽然是字符串，但它既不是 属性值，也不是 文本域的值
                '''这种情况下，必须构造一个 ChainDict 对象，以让其不那么特别

                这里有节点标签（<t id='ooo'><t>）和无节点标签（<t>ooo</t>）容易混淆，这在 xmltodict 的解析中是完全能够区分的
                # 此处处理剩余的字符串情况（标签 和 伪标签）:唯一能肯定的就是，本变量一定是标签类型无误
                '''
                temp_node = ChainDict()
                temp_node[self.real_cdata_key] = temp_node_content # text_ 域大众化
                temp_xml[k] = temp_node
                
                self.signal_add_node(temp_node, temp_xml, k, temp_node_content)
            else:
                # 属性值字符串，带属性的文本域字符串，节点（三种情况）
                # 其中节点分两种：原生 / 人为构造节点（发生在空标签的时候，如：<t></t>）
                temp_xml[k] = temp_node_content 

            # 属性搜索依据注册
            if newname_oldname is not None: # 当前对象是属性的情况，注册属性，注册属性搜索条件
                search_key = f'{k}={temp_xml[k]}'
                self.signal_add_attr(temp_xml, newname_oldname, search_key)

            # ChainDict 和 List 类型处理
            if isinstance(temp_node_content, list):
                self.signal_add_node(temp_xml[k], temp_xml, k)

            if isinstance(temp_node_content, ChainDict):
                if self.real_cdata_key not in temp_xml[k]:
                    temp_xml[k][self.real_cdata_key] = ''
                text = temp_xml[k][self.real_cdata_key]
                self.signal_add_node(temp_xml[k], temp_xml, k, text)

        return temp_xml

    def _build_chain_by_BFS(self, doc_obj: Type_OrderedDict) -> ChainDict:
        ...

    def _build_chain_by_DFS(self, doc_obj: Type_OrderedDict) -> ChainDict:
        ...

    def unbound_text(self, node: ChainDict):
        # while id(node) in self._reverse_text_node:
        #     self._reverse_text_node.remove(id(node))
        ...

    # def _unbound_attrname(self, node: ChainDict):
    #     self._reverse_attr_name.values()

    # def _unbound_searcher(self, node: ChainDict):
    #     self._attr_searcher = self.chainXML._attr_searcher

    def exists(self, node_id: int) -> bool:
        '''判断当前节点是否存在
        '''
        if node_id in list(self._contrast_ids.keys()) + list(self._contrast_ids.values()):
            return True
        else:
            return False

    def is_parent_exist(self, node_id: int) -> bool:
        '''判断当前节点是否有父节点
        '''
        if node_id in self._contrast_ids:
            return True
        else:
            return False

    def parent(self, node_id: int) -> int:
        '''获取当前节点的父节点
        '''
        if self.is_parent_exist(node_id):
            return self._contrast_ids[node_id]
        else:
            if node_id in self._contrast_ids.values():
                return node_id # if no parent, return self.
            else:
                raise NotNodeFound('node_id error.')

    def children(self, p_id: int) -> List[str]:
        '''获取当前节点的所有孩子节点
        '''
        children_node_ids = []
        for t_c, t_p in self._contrast_ids.items():
            if t_p == p_id:
                children_node_ids.append(t_c)
        return children_node_ids
    
    def siblings(self, node_id: int) -> List[int]:
        '''获取当前节点的所有兄弟节点【不包含当前节点】
        '''
        p_id = self.parent(node_id)
        siblings_node_ids = self.children(p_id)
        siblings_node_ids.remove(node_id)
        return siblings_node_ids

    def sibling_ancestor(self, node_id: str):
        '''获取当前节点的所有祖先节点，及其祖先节点的兄弟节点

        （包含当前节点及其兄弟节点）【包含当前节点】
        '''
        while self.is_parent_exist(node_id):
            yield self.siblings(node_id) + [node_id, ]
            node_id = self.parent(node_id)
        if self.exists(node_id) and not self.is_parent_exist(node_id):
            yield [node_id, ]
        else:
            yield []

    def descendants(self, node_id: int) -> List[int]:
        '''获取当前节点的所有子孙节点【不包含当前节点】
        '''
        nodes = []
        stack = []
        if self.exists(node_id):
            stack.extend(self.children(node_id))
        while stack:
            pop_node_id = stack.pop()
            nodes.append(pop_node_id)
            stack.extend(self.children(pop_node_id))
        return nodes

    def ancestor(self, node_id: str):
        '''获取当前节点的所有祖先节点
        '''
        while self.is_parent_exist(node_id):
            node_id = self.parent(node_id)
            yield [node_id, ]

    def __all_text_node_ids(self):
        '''获取所有文本域对应的节点
        '''
        return reduce(lambda x,y:x+y, self._text_ids.values())

    def _revert_data(self, forward=True):
        '''保存前后的数据转置工作（确保数据格式的解析前后一致性）
        '''
        for no, objs in self._reverse_attr_name.items():
            new_name, old_name = no
            for obj in objs:
                obj = self._id_nodes[obj]
                if forward:
                    obj[new_name] = obj[old_name]
                    del obj[old_name]
                else:
                    obj[old_name] = obj[new_name]
                    del obj[new_name]

        for node in self.__all_text_node_ids():
            node = self._id_nodes[node]
            if forward:
                node[self.real_cdata_key] = node[self.cdata_key]
                del node[self.cdata_key]
            else:
                node[self.cdata_key] = node[self.real_cdata_key]
                del node[self.real_cdata_key]
